package com.xhu.collegestudentrentalsystem.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import io.swagger.models.auth.In;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.util.Calendar;
import java.util.Date;
import java.util.Map;

/**
 *  @author 朱慧琳
 *  @date 2022/12/13 14:50
 *
 * 封装使用JWT 生成和解析 token 的工具类
 */
@Component
public class JWTUtils {
    /**
     * 1. 定义密钥
     */
    @Value("${jwt.config.secret}")
    private String secret;

    /**
     * 2. 设置过期时间
     */
    @Value("${jwt.config.tokenExpireSeconds}")
    private Integer expireSeconds;

    /**
     * 生成一个token
     *
     * @param payloadInfo   存放的简单用户信息（不能包含密码等敏感信息）
     * @param expireSeconds 设置过期时间
     * @return
     */
    public String createToken(Map<String, Object> payloadInfo, int expireSeconds) {
        // 1. 得到创建token的builder对象
        JWTCreator.Builder builder = JWT.create();

        // 2. 设置用户简单信息（payload部分信息）
        for (Map.Entry<String, Object> entry : payloadInfo.entrySet()) {
            setPayloadInfo(builder, entry.getKey(), entry.getValue());
        }

        // 3. 设置token的过期时间
        Calendar instance = Calendar.getInstance();
        instance.add(Calendar.SECOND, expireSeconds);
        builder.withExpiresAt(instance.getTime());

        // 4. 设置加密方式，并生成token并返回
        String token = builder.sign(Algorithm.HMAC256(secret));
        return token;
    }

    /**
     * 生成一个有默认过期时间的token（假设默认过期时间为1800秒）
     *
     * @param payloadInfo 存放的简单用户信息（不能包含密码等敏感信息）
     * @return
     */
    public String createToken(Map<String, Object> payloadInfo) {
        return createToken(payloadInfo, 1800);
    }

    /**
     * 校验token（如果没有抛出任何异常，则校验通过，该token合法）
     *
     * @param token 获取到的token信息
     */
    public void verify(String token) {
        // 构建校验对象，注意传入的是密钥值，且解析方式和加密方式要一致
        JWTVerifier jwtVerifier = JWT.require(Algorithm.HMAC256(secret)).build();
        // 校验（解析token）
        jwtVerifier.verify(token);
    }

    /**
     * 获取解析token后能够得到的用户简单信息
     *
     * @param token 获取到的token信息
     * @param key   在设置withClaim时的key值
     * @param clazz 要获取的数据类型
     * @param <T>
     * @return
     */
    public <T> T getValue(String token, String key, Class<T> clazz) {
        // 解析token
        DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(secret)).build().verify(token);

        return decodedJWT.getClaim(key).as(clazz);
    }

    /**
     * 如果要获取的是一个数组对象，这使用以下方法
     * 例如：要获取用户权限集合，需要得到的就是一个String[]类型
     */
    public <T> T[] getArrayValue(String token, String key, Class<T> clazz) {
        // 解析token
        DecodedJWT decodedJWT = JWT.require(Algorithm.HMAC256(secret)).build().verify(token);

        return decodedJWT.getClaim(key).asArray(clazz);
    }

    private static void setPayloadInfo(JWTCreator.Builder builder, String key, Object value) {
        Class<?> aClass = value.getClass();
        if (aClass.equals(String.class)) {
            builder.withClaim(key, (String) value);
        } else if (aClass.equals(Boolean.class)) {
            builder.withClaim(key, (Boolean) value);
        } else if (aClass.equals(Date.class)) {
            builder.withClaim(key, (Date) value);
        } else if (aClass.equals(Long.class)) {
            builder.withClaim(key, (Long) value);
        } else if (aClass.equals(Integer.class)) {
            builder.withClaim(key, (Integer) value);
        } else if (aClass.equals(Double.class)) {
            builder.withClaim(key, (Double) value);
        } else if (aClass.equals(String[].class)) {
            builder.withArrayClaim(key, (String[]) value);
        } else if (aClass.equals(Long[].class)) {
            builder.withArrayClaim(key, (Long[]) value);
        } else if (aClass.equals(Integer[].class)) {
            builder.withArrayClaim(key, (Integer[]) value);
        } else {
            throw new IllegalArgumentException("存入token中的数据不合法，存入值的类型应为：String、Boolean、Long、Integer、Double、" +
                    "Date、String[]、Long[]、Integer[] 中的任意一种，但是你传入的值的类型为： " + aClass);
        }
    }
}
